package co.yixiang.yshop.module.system.service.captcha;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import co.yixiang.yshop.module.system.controller.admin.captcha.vo.ImageCaptchaGetReqVO;
import co.yixiang.yshop.module.system.controller.admin.captcha.vo.ImageCaptchaGetRespVO;
import co.yixiang.yshop.module.system.controller.admin.captcha.vo.ImageCaptchaVerifyReqVO;
import co.yixiang.yshop.module.system.controller.admin.captcha.vo.ImageCaptchaVerifyRespVO;
import lombok.extern.slf4j.Slf4j;
import co.yixiang.yshop.module.infra.api.file.FileApi;
import co.yixiang.yshop.module.infra.api.file.FileSimpleRespDTO;
import cn.hutool.core.io.FileUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 图片选择验证码服务实现
 *
 * @author yshop
 */
@Service
@Slf4j
public class ImageCaptchaServiceImpl implements ImageCaptchaService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    
    @Value("${yshop.file-path}")
    private String filePath;
    
    @Value("${server.port:48081}")
    private String serverPort;

    @Resource
    private FileApi fileApi;

    // 验证码缓存前缀
    private static final String CAPTCHA_KEY_PREFIX = "image_captcha:";
    // 验证码过期时间（分钟）
    private static final int CAPTCHA_EXPIRE_MINUTES = 5;
    // 图片分类
    private static final String[] CATEGORIES = {"car", "house", "person", "animal", "plant", "food"};

    @Override
    public ImageCaptchaGetRespVO getImageCaptcha(ImageCaptchaGetReqVO reqVO, String browserInfo) {
        // 生成验证码token
        String token = IdUtil.simpleUUID();
        
        // 生成AES密钥
        String secretKey = IdUtil.simpleUUID().substring(0, 16);
        
        // 随机选择目标分类
        String targetCategory = CATEGORIES[RandomUtil.randomInt(0, CATEGORIES.length)];
        
        // 获取图片列表（6张图片，其中包含目标分类的图片）
        List<ImageCaptchaGetRespVO.ImageInfo> images = getRandomImages(targetCategory);
        
        // 保存验证码信息到Redis
        CaptchaData captchaData = new CaptchaData();
        captchaData.setTargetCategory(targetCategory);
        captchaData.setCorrectIndexes(getCorrectIndexes(images, targetCategory));
        captchaData.setBrowserInfo(browserInfo);
        captchaData.setSecretKey(secretKey);
        
        String cacheKey = CAPTCHA_KEY_PREFIX + token;
        stringRedisTemplate.opsForValue().set(cacheKey, 
            captchaDataToString(captchaData), 
            CAPTCHA_EXPIRE_MINUTES, TimeUnit.MINUTES);
        
        // 构建响应
        ImageCaptchaGetRespVO respVO = new ImageCaptchaGetRespVO();
        respVO.setToken(token);
        respVO.setSecretKey(secretKey);
        respVO.setTargetCategory(targetCategory);
        respVO.setImages(images);
        
        return respVO;
    }

    @Override
    public ImageCaptchaVerifyRespVO verifyImageCaptcha(ImageCaptchaVerifyReqVO reqVO, String browserInfo) {
        ImageCaptchaVerifyRespVO respVO = new ImageCaptchaVerifyRespVO();
        
        try {
            String cacheKey = CAPTCHA_KEY_PREFIX + reqVO.getToken();
            String cacheData = stringRedisTemplate.opsForValue().get(cacheKey);
            
            if (StrUtil.isBlank(cacheData)) {
                respVO.setSuccess(false);
                respVO.setMessage("验证码已过期");
                return respVO;
            }
            
            // 解析缓存数据
            CaptchaData captchaData = stringToCaptchaData(cacheData);
            
            // 验证浏览器信息
            if (!Objects.equals(captchaData.getBrowserInfo(), browserInfo)) {
                respVO.setSuccess(false);
                respVO.setMessage("验证环境异常");
                return respVO;
            }
            
            // 验证选择的图片索引
            List<Integer> selectedIndexes = reqVO.getSelectedIndexes();
            List<Integer> correctIndexes = captchaData.getCorrectIndexes();
            
            // 排序后比较
            Collections.sort(selectedIndexes);
            Collections.sort(correctIndexes);
            
            boolean isCorrect = selectedIndexes.equals(correctIndexes);
            
            if (isCorrect) {
                // 验证成功，生成验证凭证
                String captchaVerification = generateCaptchaVerification(reqVO.getToken(), captchaData.getSecretKey());
                respVO.setSuccess(true);
                respVO.setCaptchaVerification(captchaVerification);
                respVO.setMessage("验证成功");
                
                // 删除缓存
                stringRedisTemplate.delete(cacheKey);
            } else {
                respVO.setSuccess(false);
                respVO.setMessage("验证失败，请重新选择");
            }
            
        } catch (Exception e) {
            log.error("图片验证码验证异常", e);
            respVO.setSuccess(false);
            respVO.setMessage("验证异常");
        }
        
        return respVO;
    }

    /**
     * 获取随机图片列表
     */
    private List<ImageCaptchaGetRespVO.ImageInfo> getRandomImages(String targetCategory) {
        List<ImageCaptchaGetRespVO.ImageInfo> images = new ArrayList<>();

        // 读取 DB 中各分类素材
        Map<String, List<FileSimpleRespDTO>> categoryToFiles = new HashMap<>();
        for (String category : CATEGORIES) {
            String prefix = buildCaptchaPathPrefix(category);
            List<FileSimpleRespDTO> files = fileApi.getFilesByPathPrefix(prefix, 100);
            categoryToFiles.put(category, files == null ? Collections.emptyList() : files);
        }

        // 至少 1-3 张目标分类
        int targetCount = RandomUtil.randomInt(1, 4);
        addRandomImagesFromCategory(images, targetCategory, categoryToFiles.get(targetCategory), targetCount);

        // 添加其他分类，直至 6 张
        List<String> otherCategories = Arrays.stream(CATEGORIES)
                .filter(c -> !c.equals(targetCategory))
                .collect(Collectors.toList());
        while (images.size() < 6) {
            String randomCategory = otherCategories.get(RandomUtil.randomInt(0, otherCategories.size()));
            addRandomImagesFromCategory(images, randomCategory, categoryToFiles.get(randomCategory), 1);
            // 防止某些分类为空导致死循环，若所有分类都无素材，则退出
            if (categoryToFiles.values().stream().allMatch(List::isEmpty)) {
                break;
            }
        }

        // 若素材不足，返回已有数量（前端能处理）
        Collections.shuffle(images);
        return images;
    }

    private static String buildCaptchaPathPrefix(String category) {
        return "captcha/images/" + category + "/";
    }

    private void addRandomImagesFromCategory(List<ImageCaptchaGetRespVO.ImageInfo> acc,
                                             String category,
                                             List<FileSimpleRespDTO> files,
                                             int need) {
        if (files == null || files.isEmpty()) {
            log.warn("验证码素材分类无数据: {}", category);
            return;
        }
        for (int i = 0; i < need && acc.size() < 6; i++) {
            FileSimpleRespDTO dto = files.get(RandomUtil.randomInt(0, files.size()));
            ImageCaptchaGetRespVO.ImageInfo imageInfo = new ImageCaptchaGetRespVO.ImageInfo();
            imageInfo.setCategory(category);
            imageInfo.setUrl(dto.getUrl());
            imageInfo.setName(FileUtil.getName(dto.getPath()));
            acc.add(imageInfo);
        }
    }

    /**
     * 获取正确答案的索引列表
     */
    private List<Integer> getCorrectIndexes(List<ImageCaptchaGetRespVO.ImageInfo> images, String targetCategory) {
        List<Integer> correctIndexes = new ArrayList<>();
        for (int i = 0; i < images.size(); i++) {
            if (targetCategory.equals(images.get(i).getCategory())) {
                correctIndexes.add(i);
            }
        }
        return correctIndexes;
    }

    /**
     * 生成验证凭证
     */
    private String generateCaptchaVerification(String token, String secretKey) {
        // 为了与登录校验逻辑兼容，这里返回包含“---”的明文标记，
        // 后端 AdminAuthServiceImpl 通过是否包含该标记识别为图片选择验证码，
        // 从而跳过旧滑块/点选验证码的二次校验。
        // 形如：<token>---<timestamp>
        return token + "---" + System.currentTimeMillis();
    }

    /**
     * 验证码数据转字符串（简单实现，生产环境建议使用JSON）
     */
    private String captchaDataToString(CaptchaData data) {
        return data.getTargetCategory() + "|" + 
               String.join(",", data.getCorrectIndexes().stream().map(String::valueOf).collect(Collectors.toList())) + "|" +
               data.getBrowserInfo() + "|" + 
               data.getSecretKey();
    }

    /**
     * 字符串转验证码数据
     */
    private CaptchaData stringToCaptchaData(String str) {
        String[] parts = str.split("\\|");
        CaptchaData data = new CaptchaData();
        data.setTargetCategory(parts[0]);
        
        String[] indexStrs = parts[1].split(",");
        List<Integer> correctIndexes = new ArrayList<>();
        for (String indexStr : indexStrs) {
            correctIndexes.add(Integer.parseInt(indexStr));
        }
        data.setCorrectIndexes(correctIndexes);
        data.setBrowserInfo(parts[2]);
        data.setSecretKey(parts[3]);
        
        return data;
    }

    /**
     * 构建完整的图片URL - 通过API接口访问
     */
    private String buildImageUrl(String category, String fileName) {
        // 已迁移到 DB-FileClient URL，保留方法以兼容旧逻辑（不再使用）
        String serverUrl = getServerUrl();
        return serverUrl + "/admin-api/system/captcha/image/image/" + category + "/" + fileName;
    }
    
    /**
     * 获取服务器URL
     */
    private String getServerUrl() {
        // 从配置中获取端口，默认使用48081
        return "http://localhost:" + serverPort;
    }

    /**
     * 验证码数据内部类
     */
    private static class CaptchaData {
        private String targetCategory;
        private List<Integer> correctIndexes;
        private String browserInfo;
        private String secretKey;

        public String getTargetCategory() {
            return targetCategory;
        }

        public void setTargetCategory(String targetCategory) {
            this.targetCategory = targetCategory;
        }

        public List<Integer> getCorrectIndexes() {
            return correctIndexes;
        }

        public void setCorrectIndexes(List<Integer> correctIndexes) {
            this.correctIndexes = correctIndexes;
        }

        public String getBrowserInfo() {
            return browserInfo;
        }

        public void setBrowserInfo(String browserInfo) {
            this.browserInfo = browserInfo;
        }

        public String getSecretKey() {
            return secretKey;
        }

        public void setSecretKey(String secretKey) {
            this.secretKey = secretKey;
        }
    }
}